import { EChart, TEChartType, SingleReactiveComponent } from "@/models/reactiveComponent";
import { computed, ComputedRef } from "vue";
import { TDatasetServices } from "@/services/datasetServices";
import { TSqlAnalyzeService } from "@/services/sqlAnalyzeServices";
import { uniq, set, groupBy } from "lodash-es";
import { TDbServices } from "@/services/dbServices";

export function getReactData(cpID: string, sql: string, tables: string[], services: {
    dataset: TDatasetServices,
    sqlAnalyze: TSqlAnalyzeService,
    db: TDbServices,
}) {

    return computed(() => {
        let sqlCopy = sql

        return services.db.runOnTransaction(() => {
            tables.forEach(table => {
                const sourceSql = services.dataset.createSql(table, cpID)
                sqlCopy = sqlCopy.replace(new RegExp(`from ${table}`), `from (${sourceSql.value})`)
            })
            return services.db.queryAll(sqlCopy)
        }, 'ROLLBACK')

    })

}


export function getFilterUtils(model: SingleReactiveComponent, services: {
    dataset: TDatasetServices,
    sqlAnalyze: TSqlAnalyzeService,
    db: TDbServices,
}) {
    const table = services.sqlAnalyze.getTableNames(model.sqlInfo.sql)[0]

    function getData() {
        return getReactData(model.id, model.sqlInfo.sql, [table], services)
    }

    function removeFilter() {
        services.dataset.removeFilters(model.id, table)
    }

    function addFilter(expr: string) {
        model.updateInfos.forEach(info => {
            services.dataset.addFilter(model.id, info.table, `${info.field} ${expr}`)
        })

    }

    return {
        getData,
        addFilter,
        removeFilter,
    }
}


// TODO:
type TReactDataHandlerInfo = {
    seriesConfig: {};
    chartType: TEChartType;
    path: string;
    data: ComputedRef<{}[]>;
}

type TReactDataHandlerFn = (model: EChart, info: TReactDataHandlerInfo) => void

function createEChartsReactDataHandler() {

    function pieHandler(model: EChart, info: TReactDataHandlerInfo) {
        const props = Object.keys(info.data.value[0])
        let values = info.data.value as any[]

        if (props.length === 1) {
            values = values.map(v => v[props[0]])
        }

        const seriesData = { ...info.seriesConfig, data: values }

        model.options.series.push(seriesData)

    }

    function lineHandler(model: EChart, info: TReactDataHandlerInfo) {
        const props = Object.keys(info.data.value[0])
        let values = info.data.value as any[]

        const axisData = uniq(values.map(v => v[props[1]]))

        if ('data' in model.options.xAxis[0]) {
            model.options.xAxis[0].data = axisData
        }

        if ('data' in model.options.yAxis[0]) {
            model.options.yAxis[0].data = axisData
        }


        const rowData = []
        let pre = null
        let idx = 0
        const legendData = []

        for (const row of values) {

            if (pre !== null && pre != row[props[0]]) {
                const name = row[props[0]]
                model.options.series.push({ ...info.seriesConfig, name, data: [...rowData] })
                legendData.push(name)
                rowData.length = 0
                idx = 0
            }

            rowData.push([idx, row[props[2]]])
            pre = row[props[0]]
            idx++
        }

        const name = pre
        model.options.series.push({ ...info.seriesConfig, name: pre, data: [...rowData] })
        legendData.push(name)

        model.options.legend[0].data = legendData
    }

    function barHandler(model: EChart, info: TReactDataHandlerInfo) {
        const props = Object.keys(info.data.value[0])
        let values = info.data.value as any[]

        if (props.length === 2) {
            const axisData = values.map(v => v[props[0]])

            if ('data' in model.options.xAxis[0]) {
                model.options.xAxis[0].data = axisData
            }

            if ('data' in model.options.yAxis[0]) {
                model.options.yAxis[0].data = axisData
            }


            model.options.series.push({ ...info.seriesConfig, data: [...values.map(v => v[props[1]])] })
        }

        if (props.length >= 3) {
            const rowData = []
            let pre = null
            let idx = 0
            for (const row of values) {

                if (pre !== null && pre != row[props[0]]) {
                    model.options.series.push({ ...info.seriesConfig, name: row[props[0]], data: [...rowData] })
                    rowData.length = 0
                    idx = 0
                }

                rowData.push([idx, row[props[2]]])
                pre = row[props[0]]
                idx++
            }
            model.options.series.push({ ...info.seriesConfig, name: pre, data: [...rowData] })
        }

    }

    return new Map<TEChartType, TReactDataHandlerFn>(
        [
            ['pie', pieHandler],
            ['line', lineHandler],
            ['bar', barHandler],
        ]
    )

}

const mEChartsReactDataHandlerMap = createEChartsReactDataHandler()


type TEChartOptions = { series: {}[] }

export function getEChartsFilterUtils(model: EChart, services: {
    dataset: TDatasetServices,
    sqlAnalyze: TSqlAnalyzeService,
    db: TDbServices,
}) {

    function clearAllPybiSeries(options: TEChartOptions) {
        if (!Array.isArray(options.series)) {
            options.series = [options.series]
        }

        options.series = (options.series as {}[]).filter(s => !('pybiFlag' in s))
    }


    function getData() {

        const dataInfos = model.sqlInfos.map(v => {

            const ds = services.dataset.getDataset(v.dataset)

            function normalHandler(options: TEChartOptions, query: ReturnType<typeof services.db.queryAll>) {
                // source
                set(options, `${v.path}.source`, Array.from(query.toRowsArrayHasHeader()))

                if (query.rows.length > 0) {

                    // append series
                    for (let index = 1; index < query.fields.length; index++) {
                        options.series.push({ ...v.seriesConfig, name: query.fields[index], pybiFlag: null })
                    }

                    if (options.series.length === 1) {
                        options.series[0]['name'] = query.fields[0]
                    }

                }
            }

            function scatterHandler(options: TEChartOptions, query: ReturnType<typeof services.db.queryAll>) {
                if (query.rows.length > 0) {

                    // append series
                    const valueCols = query.fields.slice(1)

                    Object.entries(groupBy(query.rows, r => r[query.fields[0]])).forEach(([key, rows]) => {
                        const data = rows.map(r => valueCols.map(f => r[f]))
                        const seriesConfig = { ...v.seriesConfig, name: key, data, pybiFlag: null }
                        options.series.push(seriesConfig)
                    })

                }
            }

            const queryHandler = v.seriesConfig.type === 'scatter' ? scatterHandler : normalHandler

            return {
                seriesConfig: v.seriesConfig,
                path: v.path,
                ds,
                queryHandler,
            }
        })

        return computed(() => {

            clearAllPybiSeries(model.options)

            dataInfos.forEach(info => {

                const sql = info.ds.toSqlWithFilters(model.id)
                const query = services.db.runOnTransaction(() => {
                    const query = services.db.queryAll(sql.value)
                    return query
                }, 'ROLLBACK')

                info.queryHandler(model.options, query)
            })

            return model.options
        })
    }


    function removeFilter() {

        model.updateInfos.forEach(info => {
            services.dataset.removeFilters(model.id, info.table)
        })

    }

    function addFilter(expr: string) {
        model.updateInfos.forEach(info => {
            services.dataset.addFilter(model.id, info.table, `${info.field} ${expr}`)
        })
    }


    return {
        getData,
        addFilter,
        removeFilter,

    }
}




